﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OverStore.Configuration;
using System.Data.SqlServerCe;
using System.Collections.ObjectModel;
using System.Data;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Exceptions;
using OverStore.Configuration.Components;
using OverStore.Configuration.Builder;
using OverStore.CompositeIdentifiers;
using OverStore.Sql.TableSchema;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Configuration.ObjectRepository.Mapping;
using OverStore.Configuration.ObjectRepository.Depot;

namespace OverStore.TutorialAsTest.UnitTest.Components.MulitpartKeys
{
    [TestClass]
    public class MultipartKeysInstanceTest
    {
        private IComponentConfiguration<IMapper<SingleDayRemark>> CreateMapperImmutableKeys()
        {
            return new DelegateMapperConfiguration<SingleDayRemark>
                         {
                             GetObjectDataFunction = (instance, name, provider) =>
                                 {
                                     var normalizedName = name.ToUpperInvariant();
                                     switch (normalizedName)
                                     {
                                         case "COLDAY":
                                             return instance.Day;
                                         case "COLMONTH":
                                             return instance.Month;
                                         case "COLYEAR":
                                             return instance.Year;
                                         case "REMARKONDATE":
                                             return instance.Message;
                                         case "ORIG__COLDAY":
                                             return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).First;
                                         case "ORIG__COLMONTH":
                                             return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).Second;
                                         case "ORIG__COLYEAR":
                                             return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).Third;
                                         default:
                                             return null;
                                     }
                                 },
                             ApplyObjectDataFunction = (data, instance, provider) =>
                                 {
                                     instance.Day = data.GetValue<int>("ColDay");
                                     instance.Month = data.GetValue<int>("ColMonth");
                                     instance.Year = data.GetValue<int>("ColYear");
                                     instance.Message = data.GetValue<string>("remarkOnDate");

                                     object colDay;
                                     object colMonth;
                                     object colYear;
                                     if (data.TryGetValue("orig__colday", out colDay) &&
                                         data.TryGetValue("orig__colMonth", out colMonth) &&
                                         data.TryGetValue("orig__colYear", out colYear))
                                     {
                                         provider.PutNewIdentifier<SingleDayRemark, Key3<int, int, int>>(instance,
                                             KeyHelper.MultipartKey<int, int, int>(((int)colDay).As("ColDay"),
                                                            ((int)colMonth).As("ColMonth"), ((int)colYear).As("ColYear")));
                                     }
                                 }
                         };
        }

        private IComponentConfiguration<IMapper<SingleDayRemark>> CreateMapperMutableKeys()
        {
            return new DelegateMapperConfiguration<SingleDayRemark>
            {
                GetObjectDataFunction = (instance, name, provider) =>
                {
                    var normalizedName = name.ToUpperInvariant();
                    switch (normalizedName)
                    {
                        case "COLDAY":
                            return instance.Day;
                        case "COLMONTH":
                            return instance.Month;
                        case "COLYEAR":
                            return instance.Year;
                        case "REMARKONDATE":
                            return instance.Message;
                        case "ORIG__COLDAY":
                            return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).First;
                        case "ORIG__COLMONTH":
                            return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).Second;
                        case "ORIG__COLYEAR":
                            return provider.GetIdentifier<SingleDayRemark, Key3<int, int, int>>(instance).Third;
                        default:
                            return null;
                    }
                },
                ApplyObjectDataFunction = (data, instance, provider) =>
                {
                    instance.Day = data.GetValue<int>("ColDay");
                    instance.Month = data.GetValue<int>("ColMonth");
                    instance.Year = data.GetValue<int>("ColYear");
                    instance.Message = data.GetValue<string>("remarkOnDate");
                    provider.PutNewIdentifier<SingleDayRemark, Key3<int, int, int>>(instance,
                        KeyHelper.MultipartKey<int, int, int>(instance.Day.As("ColDay"), instance.Month.As("ColMonth"), instance.Year.As("ColYear")));
                }
            };
        }

        public OverStoreConfiguration CreateConfig(string connectionString)
        {
            return CreateConfig(connectionString, false);
        }

        public OverStoreConfiguration CreateConfig(string connectionString, bool mutableIdentifiers)
        {
            Key3<int, int, int> prototype = new Key3<int, int, int>(
                0.As("ColDay"), 0.As("ColMonth"), 0.As("ColYear"));
            return OverStoreFluent.Configure()
                .LogToConsole().ConnectToDatabase<SqlCeConnection>(connectionString, OverStore.Runtime.Storage.ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<SingleDayRemark, Key3<int, int, int>>()
                    .WithDepot(new DelegateDepotConfiguration<SingleDayRemark, Key3<int, int, int>>()
                            {
                                IdentifierGenerator = Component.IdentifierGenerator.Delegate<SingleDayRemark, Key3<int, int, int>>(
                                            (instance, last) => last.CreateCopy(instance.Day, instance.Month, instance.Year),
                                            new Key3<int, int, int>(0.As("ColDay"), 1.As("ColMonth"), 2.As("ColYear"))),
                                StoreIdentifierCopy = (instance, id) =>
                                {
                                    instance.Day = id.First;
                                    instance.Month = id.Second;
                                    instance.Year = id.Third;
                                }
                            })
                    .WithMapper(mutableIdentifiers ? CreateMapperMutableKeys() : CreateMapperImmutableKeys())
                    .CommandsFromTable(false,
                            Component.CreateTable("CompoundKeyTable")
                                     .WithColumn(new Key<int> { Name = "ColDay", IsMutable = mutableIdentifiers })
                                     .WithColumn(new Key<int> { Name = "ColMonth", IsMutable = mutableIdentifiers })
                                     .WithColumn(new Key<int> { Name = "ColYear", IsMutable = mutableIdentifiers })
                                     .Column<string>("RemarkOnDate"))
                    .WithChangesTracker(Component.InstanceChangesTracker.Checksum<SingleDayRemark, Key3<int, int, int>>("Day", "Month", "Year", "Message"))
                 .EndObjectRepository()
             .End();
        }


        [TestMethod]
        public void GetInstanceByMultipartKey()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString);
            using (var session = config.CreateSession())
            {
                var key = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                var sdr = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreEqual(12, sdr.Day);
                Assert.AreEqual(2, sdr.Month);
                Assert.AreEqual(1984, sdr.Year);

                var key1 = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                Assert.AreNotSame(key, key1);

                var sdr1 = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key1);
                Assert.AreSame(sdr, sdr1);
            }
        }

        [TestMethod]
        public void LooseKeyChangesOnReload()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString);
            using (var session = config.CreateSession())
            {
                var key = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                var sdr = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreEqual(12, sdr.Day);
                Assert.AreEqual(2, sdr.Month);
                Assert.AreEqual(1984, sdr.Year);

                sdr.Day = 15;
                sdr.Month = 5;
                sdr.Year = 2008;
                sdr.Message = "New Message";

                session.Reload(sdr);
                Assert.AreEqual(12, sdr.Day);
                Assert.AreEqual(2, sdr.Month);
                Assert.AreEqual(1984, sdr.Year);
            }
        }

        [TestMethod]
        public void AcceptKeyChangesOnSave()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString, true);
            using (var session = config.CreateSession())
            {
                var key = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                var sdr = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreEqual(12, sdr.Day);
                Assert.AreEqual(2, sdr.Month);
                Assert.AreEqual(1984, sdr.Year);

                sdr.Day = 15;
                sdr.Month = 5;
                sdr.Year = 2008;
                sdr.Message = "New Message";

                session.Save(sdr);

                var newKey = KeyHelper.MultipartKey(15.As("ColDay"), 5.As("ColMonth"), 2008.As("ColYear"));
                var remarkOnNewKey = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(newKey);
                Assert.AreSame(sdr, remarkOnNewKey);

                sdr.Day = 12;
                sdr.Month = 2;
                sdr.Year = 1984;
                session.Save(sdr);
            }
        }

        [TestMethod]
        public void CorrectInsertWithMutableKeysOnKeyChange()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString, true);
            using (var session = config.CreateSession())
            {
                var dayOfKnowledge = new SingleDayRemark
                {
                    Day = 1,
                    Month = 9,
                    Year = 2009,
                    Message = "Day of Knowledge"
                };

                // instance key captured from instance on addition
                session.Add(dayOfKnowledge);
                var key = KeyHelper.MultipartKey(1.As("ColDay"), 9.As("ColMonth"), 2009.As("ColYear"));

                var getted = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreSame(dayOfKnowledge, getted);


                // Mutate identifier parts.
                dayOfKnowledge.Day = 23;
                dayOfKnowledge.Month = 2;
                dayOfKnowledge.Year = 1999;
                session.Save(dayOfKnowledge);

                // Object should be inserted with mutated identifier.
                using (var s2 = config.CreateSession())
                {
                    var k2 = KeyHelper.MultipartKey(23.As("ColDay"), 2.As("ColMonth"), 1999.As("ColYear"));
                    var d2 = s2.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(k2);
                    Assert.AreEqual(23, d2.Day);
                    Assert.AreEqual(2, d2.Month);
                    Assert.AreEqual(1999, d2.Year);
                    Assert.AreEqual("Day of Knowledge", d2.Message);
                }
                // And object identifier should be updated correspondently to object data changes.
                var newKey = KeyHelper.MultipartKey(23.As("ColDay"), 2.As("ColMonth"), 1999.As("ColYear"));
                var newValue = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(newKey);
                Assert.AreSame(dayOfKnowledge, newValue);
            }
        }

        [TestMethod]
        public void CorrectInsertWithImmutableKeysOnKeyChange()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString, false);
            using (var session = config.CreateSession())
            {
                var dayOfKnowledge = new SingleDayRemark
                {
                    Day = 1,
                    Month = 9,
                    Year = 2009,
                    Message = "Day of Knowledge"
                };

                // instance key captured from instance on addition
                session.Add(dayOfKnowledge);
                var key = KeyHelper.MultipartKey(1.As("ColDay"), 9.As("ColMonth"), 2009.As("ColYear"));

                var getted = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreSame(dayOfKnowledge, getted);

                dayOfKnowledge.Day = 23;
                dayOfKnowledge.Month = 2;
                dayOfKnowledge.Year = 1999;
                session.Save(dayOfKnowledge);

                // IdentifierProvider parts should be updated to its original values.
                // If not, check Depot.ApplyObjectData method to be correct implemented.
                Assert.AreEqual(1, dayOfKnowledge.Day);
                Assert.AreEqual(9, dayOfKnowledge.Month);
                Assert.AreEqual(2009, dayOfKnowledge.Year);

                // Object should be inserted with original identifier captured due calling session.Add() method.
                using (var s2 = config.CreateSession())
                {
                    var k2 = KeyHelper.MultipartKey(1.As("ColDay"), 9.As("ColMonth"), 2009.As("ColYear"));
                    var d2 = s2.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(k2);
                    Assert.AreEqual(1, d2.Day);
                    Assert.AreEqual(9, d2.Month);
                    Assert.AreEqual(2009, d2.Year);
                    Assert.AreEqual("Day of Knowledge", d2.Message);
                }
                // And object identifier should not be updated  correspondently to object data changes.
                var newKey = KeyHelper.MultipartKey(1.As("ColDay"), 9.As("ColMonth"), 2009.As("ColYear"));
                var newValue = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(newKey);
                Assert.AreSame(dayOfKnowledge, newValue);
            }
        }

        [TestMethod]
        public void SessionIdentifiersUpdates()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString, true);
            using (var session = config.CreateSession())
            {
                var key = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                var sdr = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);
                Assert.AreEqual(12, sdr.Day);
                Assert.AreEqual(2, sdr.Month);
                Assert.AreEqual(1984, sdr.Year);

                sdr.Day = 15;
                sdr.Month = 5;
                sdr.Year = 2008;
                sdr.Message = "New Message1";

                session.Save(sdr);

                Assert.AreEqual(15, sdr.Day);
                Assert.AreEqual(5, sdr.Month);
                Assert.AreEqual(2008, sdr.Year);
                Assert.AreEqual("New Message1", sdr.Message);

                using (var innerSession = config.CreateSession())
                {
                    var newKey = key.CreateCopy(15, 5, 2008);
                    var innerSdr = innerSession.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(newKey);
                    Assert.AreEqual(15, innerSdr.Day);
                    Assert.AreEqual(5, innerSdr.Month);
                    Assert.AreEqual(2008, innerSdr.Year);
                    Assert.AreEqual("New Message1", innerSdr.Message);
                }

                sdr.Day = 12;
                sdr.Month = 2;
                sdr.Year = 1984;
                session.Save(sdr);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(IdentityReferenceIntegrityException))]
        public void InsertDuplicateKeysFailed()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfig(connectionString);
            using (var session = config.CreateSession())
            {
                var key = KeyHelper.MultipartKey(12.As("ColDay"), 2.As("ColMonth"), 1984.As("ColYear"));
                var sdr = session.GetByIdentifier<SingleDayRemark, Key3<int, int, int>>(key);

                var newRemark = new SingleDayRemark
                {
                    Day = 12,
                    Month = 2,
                    Year = 1984,
                    Message = "Game of soccers"
                };

                session.Add(newRemark);
            }
        }
    }
}
